/*
 *   This file is part of ed_pvr.
 *
 *   ed_pvr is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   ed_pvr is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with ed_pvr.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <memory.h>
#include <getopt.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>

#include "utils.h"
#include "device.h"
#include "timers.h"
#include "protocol.h"

#define RECORD_HTTP_OK_STR	 		"HTTP/1.0 200 OK"
#define RECORD_SOCKET_TIMEOUT		5000
#define RECORD_RW_BUFFER_SZ			(TS_PACKET_SIZE * 1360)
#define RECORD_MAX_CONCURRENT		50

TIMER_STRUCT active_list[RECORD_MAX_CONCURRENT];

int _record_http_connect(char *);
char *_record_generate_base_filename(char *, char *);
void _record_stop_recording(TIMER_STRUCT *);
int _record_compare_timers(TIMER_STRUCT *, TIMER_STRUCT *);
TIMER_STRUCT * _record_add_active(TIMER_STRUCT *);
void _record_del_active(TIMER_STRUCT *);


int _record_compare_timers(TIMER_STRUCT *timer1, TIMER_STRUCT *timer2)
{
	if (timer1 && timer2)
	{
		if (timer1->data.id == timer2->data.id && timer1->data.end_epoch == timer2->data.end_epoch &&
			timer1->data.channel_num == timer2->data.channel_num &&
			timer1->data.start_epoch == timer2->data.start_epoch)
		{
			return 1;
		}

		return 0;
	}
	return -1;
}

TIMER_STRUCT * _record_add_active(TIMER_STRUCT *timer)
{
	if (timer)
	{
		int i, j=-1;
		for(i=0; i < RECORD_MAX_CONCURRENT; i++)
		{
			//Find the first empty position, just in case we need to add
			if (j == -1 && !active_list[i].data.id && !active_list[i].data.channel_num)
				j = i;

			// Timer already in list
			if (_record_compare_timers(timer, &active_list[i]) == 1)
				break;
		}

		if (i < RECORD_MAX_CONCURRENT)
		{
			// Already in list
			return NULL;
		}
		else
		{
			// Add to list
			if (j != -1)
			{
				memcpy(&active_list[j], timer, sizeof(TIMER_STRUCT));
//				printf("[RECORD] Adding Timer to Active List (ID: %d, pos: %d)\n", active_list[j].id, j);
				return &active_list[j];
			}
		}
	}
	return NULL;
}

void _record_del_active(TIMER_STRUCT *timer)
{
	//printf("_record_del_active\n");
	if (timer)
		memset(timer, 0, sizeof(TIMER_STRUCT));
}

char *_record_generate_base_filename(char *dir, char *name)
{
	char * ret = NULL;
	if (dir && name)
	{
		char *_dir = strdup(dir);
		ret = calloc(1, TIEMR_STRUCT_MAX_STRLEN);
		if (ret)
		{
			int count = strlen(_dir) - 1;
//			printf("HMM: (%c) (%c)\n", _dir[count], _dir[count-1]);
			while (_dir[count] == '/')
				_dir[count--] = '\0';

			snprintf(ret, TIEMR_STRUCT_MAX_STRLEN - 1, "%s/%s", _dir, name);
		}
		if (_dir)
			free(_dir);
	}
	return ret;
}

int _record_http_connect(char *url)
{
	int ret = 0;
	if (url)
	{
		UTILS_HOSTPORT_STRUCT * hostp = utils_split_host(url);
		if (hostp)
		{
			int i, sz, err;
			char req[4096] = {0};
			char *rest = NULL;

			for(i=strlen(url); i > 0; i--)
			{
				if (url[i] == '/')
				{
					rest = &url[i+1];
					break;
				}
			}

			sprintf(req, "GET /EXT/%s HTTP/1.0\n\n", rest);

//			printf("_record_http_connect: %s %d:\n%s\n", hostp->hostname, hostp->port, req);

			ret = doconnect(hostp->hostname, hostp->port, RECORD_SOCKET_TIMEOUT);
			if (ret > 0)
			{
				sz = write_bytes_to_socket(ret, (uint8_t*)req, strlen(req), RECORD_SOCKET_TIMEOUT, NULL);
				if (sz == strlen(req))
				{
					memset(&req, 0, sizeof(req));
					sz = get_bytes_from_socket(ret, (uint8_t*)&req, 17, 0, &err);
					if (sz == 17)
					{
						if (memcmp(&req, RECORD_HTTP_OK_STR, strlen(RECORD_HTTP_OK_STR)) == 0)
						{
							int count = 0, j = 2;
							uint8_t in = 0, last = 0;

							do
							{
								last = in;
								sz = get_bytes_from_socket(ret, (uint8_t*)&in, 1, 0, &err);
								count++;
//								printf("E2a: %d (0x%x 0x%x)\n", sz, last, in);
								if (last == 0xd && in == 0xa)
									j--;
								if (count > TS_PACKET_SIZE)
									break;
							} while(j);

							if (j)
							{
								close(ret);
								ret = -1;
							}
						}
					}
					else
					{
						close(ret);
						ret = -1;
					}
				}
				else
				{
					close(ret);
					ret = -1;
				}

			}
			else
			{
				close(ret);
				ret = -1;
			}
			free(hostp);
		}
	}
//	printf("Retuning: %d\n", ret);
	return ret;
}

int _record_generate_rec_file(char *filename, TIMER_STRUCT *timer)
{
	if (filename && timer)
	{
		char fn[MAX_FILENAME_SIZE] = {0};
		snprintf(fn, MAX_FILENAME_SIZE, "%s.rec", filename); 
		FILE *fp = fopen(fn, "wb+");

		if (fp)
		{
			timer_print_timer(timer);
			fprintf(fp, "%s.idx\n", filename);
			fprintf(fp, "%s.mpg\n", filename);
			fprintf(fp, "%li\n", timer->data.start_epoch);
			fprintf(fp, "%li\n", timer->data.end_epoch);
			fprintf(fp, "%" PRId64"\n", timer->data.channel_num);
			fprintf(fp, "%s\n", timer->data.channel_name);
			fprintf(fp, "%s\n", timer->data.url);
			fprintf(fp, "%s\n", timer->data.title);
			fprintf(fp, "%s\n", timer->data.plot);
			fprintf(fp, "%s\n", timer->data.plot_outline);
			fprintf(fp, "%s\n", timer->data.genre);
			fprintf(fp, "%s\n", timer->data.sub_genre);

			fclose(fp);

			snprintf(fn, MAX_FILENAME_SIZE, "%s.idx", filename); 
			fp = fopen(fn, "wb+");
			if (fp)
				fclose(fp);

			return 0;
		}

	}
	return 1;
}



#ifdef _MAIN_

uint64_t channel_num = 0, start_epoch = 0, end_epoch = 0;
int die = 0;

char *recording_path = NULL;
char *remote_ip = NULL;

static const char *optString = "s:e:c:r:i:a:?h";
static const struct option longOpts[] =
{
	{ "help", no_argument, NULL, 'h' },
	{ NULL, no_argument, NULL, 0 }
};

uint8_t get_opts(int argc, char *const argv[])
{
	int index = 0;
	int opt = getopt_long( argc, argv, optString, longOpts, &index );

	if (opt == -1 )
	{
//		display_usage(argv[0]);
		return 1;
	}


	while ( opt != -1 )
	{
		switch ( opt )
		{
			case 'a':
				remote_ip = optarg;
				break;

			case 'r':
				recording_path = optarg;
				break;

			default:
				break;
		}
		opt = getopt_long( argc, argv, optString, longOpts, &index );
	}
	return 0;
}

void sigpipe_handler() {}
void sigusr1_handler() {}
void sigusr2_handler() {exit(0);}

int _record_id_in_list(int, TIMER_STRUCT *);
void *_record_process_thread(void *);
uint64_t _record_get_timer_seqnum(UTILS_HOSTPORT_STRUCT *);
TIMER_STRUCT * _record_get_timer_list(UTILS_HOSTPORT_STRUCT *, uint32_t *);


void *_record_process_thread(void *p)
{
//	printf("[RECORD] _record_process_thread start\n");
	if (p)
	{

		TIMER_STRUCT *timer = (TIMER_STRUCT *)p;
		FILE *fp = NULL;
		char tmp_filename[TIEMR_STRUCT_MAX_STRLEN] = {0};
		int buffer_sz = RECORD_RW_BUFFER_SZ;
		char *filename_base = _record_generate_base_filename(timer->recording_dir, timer->filename);
		uint8_t *buffer = malloc(buffer_sz);
		int64_t _tnow = (int64_t)time(NULL);

//		printf("[RECORD] Record to: %s (thread start)\n", filename_base);
		printf("[RECORD] Start record now, ID: %d (%s)\n", timer->data.id, (char*)timer->extra_data);
		printf("\t[%s]\n", ctime(&_tnow));

		if (filename_base && buffer)
		{
			int rc = 0, err = 0, n;
			snprintf(tmp_filename, TIEMR_STRUCT_MAX_STRLEN - 1, "%s.mpg", filename_base);
			int64_t now = (int64_t)time(NULL);

			fp = fopen(tmp_filename, "wb+");

			_record_generate_rec_file(filename_base, timer);

//			printf("[RECORD] read/write1: %" PRId64" (socket: %d)\n", now, timer->socket);

			while(now < timer->data.end_epoch && now >= timer->data.start_epoch)
			{
				rc = get_bytes_from_socket(timer->socket, buffer, buffer_sz, 0, &err);
				if (rc == buffer_sz)
				{
					n = fwrite(buffer, buffer_sz, 1, fp);
					if (n != 1)
					{
						printf("[RECORD] fwrite bad\n");
						break;
					}
				}
				else
				{
					usleep(1000*1000);
				}

				now = (int64_t)time(NULL);

/*
				if (now - tmp_time > 1)
				{
//					printf("TIME, now: %" PRId64", Start: %" PRId64", End: %" PRId64"\n", now, timer->data.start_epoch,  timer->data.end_epoch);
					tmp_time = now;
				}
*/
			}
			fclose(fp);
		}

		snprintf(tmp_filename, TIEMR_STRUCT_MAX_STRLEN-1, "%s.idx", filename_base); 

		if (file_exists(tmp_filename) == 0)
		{
			remove(tmp_filename);
		}

		_record_stop_recording(timer);
		_record_del_active(timer);

		_tnow = (int64_t)time(NULL);
		printf("[RECORD] Stop record now, ID: %d\n", timer->data.id);
		printf("\t[%s]\n", ctime(&_tnow));

//		printf("[RECORD] Record to: %s (thread end)\n", filename_base);

		if (buffer)
			free(buffer);

		if (filename_base)
		{
			mpegts_io_regen_idx(filename_base, 0, 0);
			free(filename_base);
		}
	}
	return NULL;
}

uint64_t _record_get_timer_seqnum(UTILS_HOSTPORT_STRUCT * hostp)
{
	if (hostp)
	{
		int sock = 0, err = 0, sz;
		protocol_get_timer_seqnum(hostp->hostname, hostp->port, &sock);
		if (sock > 0)
		{
			uint64_t seq = 0;
			sz = get_bytes_from_socket(sock, (uint8_t*)&seq, sizeof(seq), RECORD_SOCKET_TIMEOUT, &err);
			close(sock);
			if (sz == sizeof(seq))
				return seq;
		}
	}
	return 0;
}

TIMER_STRUCT * _record_get_timer_list(UTILS_HOSTPORT_STRUCT * hostp, uint32_t *co)
{
	TIMER_STRUCT * ret = NULL;
	if (hostp)
	{
		int sock = 0, sz, err = 0;
		protocol_get_timer_list(hostp->hostname, hostp->port, &sock);
		if (sock > 0)
		{
			TIMER_STRUCT_SEND timer = {0};
			TIMER_STRUCT *tmp_timer = NULL;
			uint32_t count = 0;
			sz = get_bytes_from_socket(sock, (uint8_t*)&count, sizeof(count), RECORD_SOCKET_TIMEOUT, &err);
			printf("count: %d\n", count);
			if (co)
				memcpy(co, &count, sizeof(count));

			while(count && timer.is_last == 0)
			{
				sz = get_bytes_from_socket(sock, (uint8_t*)&timer, sizeof(TIMER_STRUCT_SEND), RECORD_SOCKET_TIMEOUT, &err);
				if (sz == sizeof(TIMER_STRUCT_SEND))
				{
					TIMER_STRUCT *_timer = calloc(1, sizeof(TIMER_STRUCT));
					if (_timer)
					{
						memcpy(&_timer->data, &timer, sz);
						if (!ret)
						{
							tmp_timer = ret = _timer;
						}
						else
						{
							tmp_timer->next = _timer;
							tmp_timer = _timer;
						}
//						timer_print_timer(_timer);
						timer_print_timer2(_timer);
					}
				}
			}
			close(sock);
			return ret;
		}
	}
	return NULL;
}


int _record_id_in_list(int id, TIMER_STRUCT *first)
{
	if (id && first)
	{
		TIMER_STRUCT *tmp = first;
		while(tmp)
		{
			if (id == tmp->data.id)
				return 1;
			tmp = tmp->next;
		}
		return 0;
	}
	return -1;
}

void sigint_handler()
{
	die = 1;
}

void _record_stop_recording(TIMER_STRUCT *timer)
{
	//printf("_record_stop_recording\n");

	if (timer)
	{
		if (timer->extra_data)
			free(timer->extra_data);
		timer->extra_data = NULL;

		if (timer->socket)
			close(timer->socket);
		timer->socket = 0;
	}
	return;
}


int main(int argc, char *argv[])
{
	UTILS_HOSTPORT_STRUCT *hostport = NULL;

	signal(SIGPIPE, sigpipe_handler);
	signal(SIGINT, sigint_handler);
	signal(SIGUSR1, sigusr1_handler);
	signal(SIGUSR2, sigusr2_handler);

	get_opts(argc, argv);

	if (remote_ip && recording_path)
	{
		int i;
		int64_t now = 0;
		uint64_t seqnum = -1;
		TIMER_STRUCT *timer_current = NULL, *timer_previous = NULL, *timer_tmp = NULL;

		for(i=0; i < RECORD_MAX_CONCURRENT; i++)
			memset(&active_list[i], 0, sizeof(TIMER_STRUCT));

		hostport = utils_split_host(remote_ip);
		printf("[RECORD] Remote Tuner, IP: %s:%d, Record Path: %s\n", hostport->hostname, hostport->port, recording_path);

		while (!die)
		{
			now = (int64_t)time(NULL);
			timer_tmp = timer_current;
			while(timer_tmp)
			{
				//printf("HMM, id: %d (status: %d)\n", timer_tmp->id, timer_tmp->status);

				if (!timer_tmp->extra_data && now >= timer_tmp->data.start_epoch && now < timer_tmp->data.end_epoch)
				{
					TIMER_STRUCT *t_pass = _record_add_active(timer_tmp);

					if (t_pass)
					{
						char tmp_url[4096] = {0};
						snprintf(tmp_url, 4095, "%s/%" PRId64"", t_pass->data.url, t_pass->data.channel_num);
						printf("[RECORD] Connecting to: %s\n", tmp_url);

						strncpy(t_pass->recording_dir, recording_path, TIEMR_STRUCT_MAX_STRLEN-1);
						snprintf(t_pass->filename, TIEMR_STRUCT_MAX_STRLEN-1, "%" PRId64"_%" PRId64"_%" PRId64"",t_pass->data.start_epoch,t_pass->data.end_epoch,t_pass->data.channel_num);
						t_pass->extra_data = (void*)_record_generate_base_filename(t_pass->recording_dir, t_pass->filename);

						t_pass->socket = _record_http_connect(tmp_url);
						if (t_pass->socket > 0)
						{
							pthread_attr_t attr;
							pthread_attr_init(&attr);
							pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
							pthread_create(&t_pass->read_thread, &attr, _record_process_thread, (void*)t_pass);
							pthread_attr_destroy(&attr);
						}
					}
					else
					{
						timer_tmp->extra_data = (void*)&timer_tmp;
//						printf("[RECORD] Not recording, already doing so? (ID: %d)\n", timer_tmp->id);
					}
				}
				timer_tmp = timer_tmp->next;
			}

			switch (protocol_alive(hostport->hostname, hostport->port, 0))
			{
				case 1:
				{
//					printf("alive: 1\n");
					uint64_t s = _record_get_timer_seqnum(hostport);
					if (seqnum != s)
					{
						uint32_t count;
						printf("[RECORD] Updating Timer List (seqnum: %" PRId64" [ %" PRId64"])\n", s, seqnum);

						while(timer_previous)
						{
							timer_tmp = timer_previous;
							timer_previous = timer_previous->next;

							if (timer_tmp)
							{
								timer_tmp->extra_data = NULL;
								_record_stop_recording(timer_tmp);
							}
						}

						timer_previous = timer_current;
						timer_current = _record_get_timer_list(hostport, &count);

						if (timer_current == NULL)
						{
							printf("[RECORD] No Timers\n");
						}
/*
						if (1)
						{
							timer_tmp = timer_current;
							while(timer_tmp)
							{
								timer_print_timer2(timer_tmp);
								timer_tmp = timer_tmp->next;
							}
						}
*/
						seqnum = s;
					}
				}
				break;

				case -1:
				case 0:
					printf("[RECORD] Remote down? (%s:%d)\n", hostport->hostname, hostport->port);
					seqnum = -1;
//					usleep(1000*1000);
				break;
			}
			usleep(1000*2000);
		}
	}
	return -1;
}

#endif
